Skip to content

added flag for clearing log files at restart #3203

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

seun-ja
Copy link
Contributor

@seun-ja seun-ja commented Jul 18, 2025

Aims to fix #3130

This PR introduces a new flag truncate-logs. It is used with the up command like this:

spin up --truncate-logs

Test

Due to the complexity related to testing it. It was manually tested.

Initially, ran the default spin up after building the spin app, the log files (test_component_stdout.txt & test_component_stderr.txt) were populated with logs, reran it, and checked the logs; the logs from the previous run were still there.

Finally, ran spin up --truncate-logs, checked the logs, and they were empty.

@@ -54,6 +55,14 @@ pub struct FactorsTriggerCommand<T: Trigger<B::Factors>, B: RuntimeFactorsBuilde
)]
pub log: Option<PathBuf>,

/// Whether to refresh logs when restarted.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found the word "refresh" a bit odd here, although I can see the connection when I squint. At least in help text, I think we should be more explicit about what the flag does, e.g.

Suggested change
/// Whether to refresh logs when restarted.
/// If set, Spin deletes the log files before starting the application.

(or 'clears the log files'? Not sure which is better)

/// Whether to refresh logs when restarted.
#[clap(
name = REFRESH_LOGS,
short = 'r',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't spend a valuable shortcode on this.

@@ -192,6 +198,8 @@ pub struct TomlResolver<'a> {
state_dir: UserProvidedPath,
/// Explicitly provided log directory.
log_dir: UserProvidedPath,
/// Whether to refresh logs when restarted.
refresh_logs: bool,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit strange to see this in a struct called TomlResolver - it's not like it has anything to do with resolving TOML. But I guess maybe the log directory is already non-TOML-related and the struct has just grown beyond its original name? Not sure.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somewhat, yes.

All but refresh_logs still source the default value from table: TomlKeyTracker<'a>, so the initial name makes sense. However, with other elements added, a change of name would be better, something like RuntimeConfigurationResolver. Would that be fine, or what would you suggest?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should have come back to this comment after writing the one about the location of the delete code - sorry.

We should first figure out a good place for the delete code to live, whether in this struct impl or elsewhere. Then that will guide us as to whether this flag needs to live here or not.

@@ -268,7 +278,17 @@ impl<'a> TomlResolver<'a> {
}

match log_dir {
UserProvidedPath::Provided(p) if self.refresh_logs => Ok(Some({
let _ = std::fs::remove_dir_all(&p);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to end up duplicating the check and the deletion code. It seems like something you could do outside the match, if the match successfully resolves a path.

But I see this is in a function whose job is to "get the configured log directory." Getting path to a directory should not have a potential side effect of deleting that directory - that's super surprising behaviour.

(Similarly, notice that this function does not create the directory. It just gets the path.)

So I think this code needs to move somewhere else, at which point hopefully we will know where the log directory is (if we have one) and can clear it without resorting to repeated guards or code.

@@ -54,6 +55,14 @@ pub struct FactorsTriggerCommand<T: Trigger<B::Factors>, B: RuntimeFactorsBuilde
)]
pub log: Option<PathBuf>,

/// Whether to refresh logs when restarted.
#[clap(
name = REFRESH_LOGS,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe consider a SPIN_ prefix for this? Going by the original issue, some folks will be treating this as a user preference and setting it in .profile rather than on the spin up command line, so it would be good to have it make sense out of the Spin context perhaps?

(sorry forgot to think of this while reviewing)

@@ -147,6 +143,11 @@ where

let toml = toml_resolver.toml();
let log_dir = toml_resolver.log_dir()?;

if refresh_logs {
let _ = Self::resolve_log_dir(&log_dir);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sorry to be a nag but once again this is kind of jamming the delete code in somewhere that yes, it's convenient right now because this code knows what the log directory is, but will be surprising to anyone debugging in a few months. This is the constructor for the runtime configuration. It has nothing to do with creating or deleting directories.

What I really think we should do before diving into writing code is some design:

  • What is the desired behaviour of the feature?
  • What considerations do we have to think about when implementing the feature?
  • What code object(s) are responsible for the behaviour?

Let me give some examples.

  • The current behaviour is to delete the logs directory. Suppose, for some reason known only to myself, I set the logs directory to be my application directory (spin up --log-dir . --refresh-logs). Oops, Spin just deleted my application directory. Should the behaviour be to delete individual log files rather than the whole directory?
  • The current implementation deletes the directory as part of trigger startup. That means that if I have a multi-trigger application, each trigger will perform a directory delete as it starts. Suppose one trigger fails early, and another trigger starts slowly. The second trigger blew away my logs for the failure.
  • The existing log file creation happens in ComponentStdioWriter, in the spin_trigger::cli::stdio module. Does deleting existing logs fit nicely with the existing responsibilities of that type or module? I'm not saying it does, I'm just giving it as an example of a place I'd look at before the runtime-config subsystem.

I don't know if there are other factors to consider because I've not done any design on this, I just feel like this might run smoother if you were gathering feedback on a holistic proposal rather than trying to make local tweaks in response to a stream of comments. That said, this is your project and if you feel more comfortable working at the code level then no worries!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Taking a further look at this issue in the broader context (apologies for not doing this earlier), I am struggling to find an intersection between up's subsequent calls/methods and trigger's subcommand where refresh-log and log-dir can be accessed.

Here's an approach I'm considering

Would you be open to a pair?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch on ComponentStdioWriter and sorry for the bad steer. I do think that module as a whole will repay attention. StdioLoggingExecutorHooks is the other half of the equation, and that does get to participate in trigger startup. Looking at other hooks (e.g. InitialKvSetter), configure_app is where they do their "do once at startup" work - we'd have to figure out the list of log files relevant to the trigger, and I'm not sure if the ConfiguredApp is pared back to just the trigger components at this point. But testing will clarify that (or more code reading!).

@seun-ja seun-ja requested a review from itowlson July 22, 2025 20:09
Copy link
Collaborator

@itowlson itowlson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this - it looks good! I had a couple of style and naming nits, which are non-blockers but I'd be keen to see in; but my only remaining functional concern is the multi-trigger-type scenario. If you could validate the each trigger only truncates its own log files then that would be very reassuring!

Indeed, generally, since this is hard to write unit tests for, it would also be very useful to have some notes in the PR about what you tested and how.

Thanks again for persevering with this!

@@ -328,3 +369,11 @@ fn bullet_list<S: std::fmt::Display>(items: impl IntoIterator<Item = S>) -> Stri
.collect::<Vec<_>>()
.join("\n")
}

fn santized_log_path(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
fn santized_log_path(
fn sanitized_log_path(

STDOUT_LOG_FILE_SUFFIX,
) {
if let Err(err) = std::fs::File::create(stdout_log_path) {
//TODO: figure out if we want to return error here
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you have it now, create will fail if the logs dir doesn't already exist, and that shouldn't be considered an error or warning. But you might want to move it to after the "create the log directory" bit so that it doesn't fail...

@@ -95,6 +103,39 @@ impl<F: RuntimeFactors, U> ExecutorHooks<F, U> for StdioLoggingExecutorHooks {
configured_app: &spin_factors::ConfiguredApp<F>,
) -> anyhow::Result<()> {
self.validate_follows(configured_app.app())?;

if self.refresh_log {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a very long block which now dominates the configure_app function. Consider breaking out e.g. if self.refresh_log { self.truncate_log_files(); }

if self.refresh_log {
let sanitized_component_ids: Vec<String> = configured_app
.app()
.components()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did we confirm that .components() returns only the components being run by this trigger?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, returns only this trigger

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent!

/// If set, Spin deletes the log files before starting the application.
#[clap(
name = SPIN_REFRESH_LOGS,
long = "refresh-logs",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd really like to align on the language here. Is the consensus for "refresh logs"? It feels ambiguous to me and I'd prefer "clear logs" or "truncate logs" or something. (But if we do make a change then the language needs to line up everywhere, not just on this line - field names, EVs, docs, etc.)

@@ -54,6 +55,13 @@ pub struct FactorsTriggerCommand<T: Trigger<B::Factors>, B: RuntimeFactorsBuilde
)]
pub log: Option<PathBuf>,

/// If set, Spin deletes the log files before starting the application.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given the proposed implementation, consider e.g.

Suggested change
/// If set, Spin deletes the log files before starting the application.
/// If set, Spin clears the log files before starting the application.

(since we no longer delete them)

@@ -139,6 +147,8 @@ pub struct FactorsConfig {
pub follow_components: FollowComponents,
/// Log directory for component stdout/stderr.
pub log_dir: UserProvidedPath,
/// If set, Spin deletes the log files before starting the application.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// If set, Spin deletes the log files before starting the application.
/// If set, Spin clears the log files before starting the application.

@seun-ja seun-ja force-pushed the clear-log-files-when-re-building-locally branch from 254bf4d to 41a7783 Compare July 22, 2025 22:19
@seun-ja seun-ja requested a review from itowlson July 22, 2025 23:03
Copy link
Collaborator

@itowlson itowlson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good - thanks! It would be great if you could write something about testing but the code looks fine!

&sanitized_component_id,
STDOUT_LOG_FILE_SUFFIX,
) {
let _ = std::fs::File::create(stdout_log_path);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you don't need the let here - the _ suffices as a discard indicator.

Suggested change
let _ = std::fs::File::create(stdout_log_path);
_ = std::fs::File::create(stdout_log_path);

(I may be mis-remembering though)

&sanitized_component_id,
STDERR_LOG_FILE_SUFFIX,
) {
let _ = std::fs::File::create(stderr_log_path);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comment (and same disclaimer!)

- added flag for clearing log files at restart
Signed-off-by: Aminu 'Seun Joshua <seun.aminujoshua@gmail.com>

- refactor: renaming + login
Signed-off-by: Aminu 'Seun Joshua <seun.aminujoshua@gmail.com>

- refactor: creates instead of delete
Signed-off-by: Aminu 'Seun Joshua <seun.aminujoshua@gmail.com>

- warn of error
Signed-off-by: Aminu 'Seun Joshua <seun.aminujoshua@gmail.com>

- revert format
Signed-off-by: Aminu 'Seun Joshua <seun.aminujoshua@gmail.com>

- some renaming + cleaner code
Signed-off-by: Aminu 'Seun Joshua <seun.aminujoshua@gmail.com>

- clean up
Signed-off-by: Aminu 'Seun Joshua <seun.aminujoshua@gmail.com>
@seun-ja seun-ja force-pushed the clear-log-files-when-re-building-locally branch from 3bcb269 to 1edd2d5 Compare July 23, 2025 09:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Clear log files when re-building locally
2 participants